#include "tile/tilemap.hpp"

#include "actor.hpp"
#include "game.hpp"
#include "graphics.hpp"
#include "tile/emptytile.hpp"
#include "util/stringutil.hpp"

namespace nmc
{
	TileMap::TileMap(int width, int height)
		:mWidth(width),
		mHeight(height)
	{
		mTiles = new Tile*[width*height];
		mEmptyTile = new EmptyTile(Point(0,0), this);
	}

	TileMap::~TileMap()
	{
		delete[] mTiles;
		delete mEmptyTile;
	}
	
	void TileMap::setTile(Tile* tile, int x, int y)
	{
		mTiles[x + y * mWidth] = tile;
	}

	Tile* TileMap::getTile(int x, int y)
	{
		// If a tile is requested that lies outside of the
		// tile map we simply return an empty tile.
		if (x < 0 || x >= mWidth || y < 0 || y >= mHeight)
		{
			return mEmptyTile;
		}

		return mTiles[x + y * mWidth];
	}

	void TileMap::draw(Graphics* graphics, Tile::Layer layer)
	{
		
	}

	void TileMap::drawTypes(Graphics* graphics)
	{
		for (int y = 0; y < mHeight; y++)
		{
			for (int x = 0; x < mWidth; x++)
			{
				getTile(x, y)->drawType(graphics);
			}
		}
	}

	void TileMap::drawSolidities(Graphics* graphics)
	{
		for (int y = 0; y < mHeight; y++)
		{
			for (int x = 0; x < mWidth; x++)
			{
				getTile(x, y)->drawSolidity(graphics);
			}
		}
	}

	void TileMap::calculateSolidities()
	{
		for (int y = 0; y < mHeight; y++)
		{
			for (int x = 0; x < mWidth; x++)
			{
				getTile(x, y)->calculateSolidity(this);
			}
		}
	}

	void TileMap::resolveCollision(Actor* actor, Point& velocity)
	{
        if (isStandingOnFloor(actor, actor->getPosition()))
        {
            
            Point newVelocity = velocity;
            resolveCollision(actor, actor->getPosition(), newVelocity);

            velocity = newVelocity;

            //Point newVelocity = velocity;
            // followFloor(actor, actor->getPosition(), newVelocity);
            //velocity = newVelocity;
        }
        else
        {
            Point newVelocity = velocity;
            resolveCollision(actor, actor->getPosition(), newVelocity);

            // if (isStandingOnFloor(actor, actor->getPosition() + newVelocity)
            //{
            //}
            velocity = newVelocity;
        }

        /*
		for (int y = 0; y < mHeight; y++)
		{
			for (int x = 0; x < mWidth; x++)
			{
				Tile* tile = getTile(x, y);
				tile->setCollisionChecked(false);
			}
		}

		Point futurePosition = actor->getPosition() + velocity;

		int startX = futurePosition.x / Tile::SIZE;
		int endX = (futurePosition.x + actor->getWidth() - 1) / Tile::SIZE + 1;
		int startY = futurePosition.y / Tile::SIZE;
		int endY = (futurePosition.y + actor->getHeight() - 1) / Tile::SIZE + 1;

		for (int y = startY; y < endY; y++)
		{
			for (int x = startX; x < endX; x++)
			{
				Tile* tile = getTile(x, y);
				tile->resolveCollision(actor, velocity);
				//tile->setCollisionChecked(true);
			}
		}

		if (isStandingOnFloor(actor) && velocity.x != 0)
		{
			int x = actor->getBottomCollisionPoint().x / Tile::SIZE;
			int y = actor->getBottomCollisionPoint().y / Tile::SIZE;
			
			Tile* center = getTile(x, y);
            Tile* centerBottom = getTile(x, y + 1);

			futurePosition = actor->getBottomCollisionPoint() + velocity;
			x = futurePosition.x / Tile::SIZE;
			y = futurePosition.y / Tile::SIZE;
		
			Tile* futureTop = getTile(x, y - 1);
			Tile* futureCenter = getTile(x, y);
			Tile* futureBottom = getTile(x, y + 1);
			
			// If the actor is moving to the right
			if (velocity.x > 0)
			{
				if (center->getType() == Tile::SLOPING_LEFT
					&& futureCenter == center)
				{
					center->stickToFloor(actor, velocity);
					return;
				}

				if (center->getType() == Tile::EMPTY
                    && centerBottom->getType() == Tile::SLOPING_LEFT)
				{
					futureCenter->stickToFloor(actor, velocity);
					return;
				}

				if (center->getType() == Tile::SLOPING_LEFT
					&& futureCenter != center
                    && futureCenter->getType() == Tile::SLOPING_LEFT)
				{
					futureCenter->stickToFloor(actor, velocity);
					return;
				}

                if (center->getType() == Tile::SLOPING_LEFT
					&& futureCenter != center
                    && futureCenter->getType() != Tile::SLOPING_LEFT
                    && futureTop->getType() == Tile::SLOPING_LEFT)
				{
					futureTop->stickToFloor(actor, velocity);
					return;
				}
			}

			// If the actor is moving to the left.
			if (velocity.x < 0)
			{
                if (futureCenter->getType() == Tile::EMPTY
					&& futureBottom->getType() == Tile::SLOPING_LEFT)
				{
					futureBottom->stickToFloor(actor, velocity);
					return;
				}
                
                if (center->getType() == Tile::SLOPING_LEFT
                    && center == futureCenter)
				{
					center->stickToFloor(actor, velocity);
					return;
				}	
                
                if (center->getType() == Tile::SLOPING_LEFT
                    && futureCenter->getType() == Tile::EMPTY)
				{
					futureBottom->stickToFloor(actor, velocity);
					return;
				}
			}
		}

        */
	}

    void TileMap::resolveCollision(Actor* actor, const Point& position, Point& velocity)
    {
		for (int y = 0; y < mHeight; y++)
		{
			for (int x = 0; x < mWidth; x++)
			{
				Tile* tile = getTile(x, y);
				tile->setCollisionChecked(false);
			}
		}

		Point futurePosition = position + velocity;

		int startX = futurePosition.x / Tile::SIZE;
		int endX = (futurePosition.x + actor->getWidth() - 1) / Tile::SIZE + 1;
		int startY = futurePosition.y / Tile::SIZE;
		int endY = (futurePosition.y + actor->getHeight() - 1) / Tile::SIZE + 1;

		for (int y = startY; y < endY; y++)
		{
			for (int x = startX; x < endX; x++)
			{
				Tile* tile = getTile(x, y);
				tile->resolveCollision(actor, velocity);
				tile->setCollisionChecked(true);
			}
		}
    }

	bool TileMap::isStandingOnFloor(Actor* actor, const Point& position)
	{
		Point actorBCP = actor->getBottomCollisionPoint();
		Tile* tile = getTile(actorBCP.x / Tile::SIZE,
							 (actorBCP.y + 1) / Tile::SIZE);

        if (tile->isStandingOnFloor(actor))
        {
            return true;
        }
       
		tile = getTile(actor->getBottomLeftPoint().x / Tile::SIZE,
					   (actor->getBottomLeftPoint().y + 1) / Tile::SIZE);

		if (tile->isStandingOnFloor(actor))
		{
			return true;
		}

		tile = getTile(actor->getBottomRightPoint().x / Tile::SIZE,
					   (actor->getBottomRightPoint().y + 1) / Tile::SIZE);

	    if (tile->isStandingOnFloor(actor))
		{
			return true;
		}

		return false;
	}
}
